/*
 * Copyright (C) 2015-2017 Alibaba Group Holding Limited
 */
#ifdef EN_COMBO_NET
#include <stdarg.h>

#include <aos/kernel.h>
#include "cJSON.h"
#include <wm_type_def.h>
#include <wm_efuse.h>

#include "app_main.h"
#include "app_sys.h"
#include "combo_net.h"
#include "device_state_manger.h"
#include "smart_outlet.h"

extern uint8_t *os_wifi_get_mac(uint8_t mac[6]);
extern int      vendor_get_product_key(char *product_key, int *len);
extern int      vendor_get_product_secret(char *product_secret, int *len);
extern int      vendor_get_device_name(char *device_name, int *len);
extern int      vendor_get_device_secret(char *device_secret, int *len);
extern int      vendor_get_product_id(uint32_t *pid);
extern int      ieee80211_is_beacon(uint16_t fc);
extern int      ieee80211_is_probe_resp(uint16_t fc);
extern int      ieee80211_get_bssid(uint8_t *in, uint8_t *mac);
extern int      ieee80211_get_ssid(uint8_t *beacon_frame, uint16_t frame_len, uint8_t *ssid);

#define HAL_IEEE80211_IS_BEACON     ieee80211_is_beacon
#define HAL_IEEE80211_IS_PROBE_RESP ieee80211_is_probe_resp
#define HAL_IEEE80211_GET_SSID      ieee80211_get_ssid
#define HAL_IEEE80211_GET_BSSID     ieee80211_get_bssid
#define HAL_AWSS_OPEN_MONITOR       HAL_Awss_Open_Monitor
#define HAL_AWSS_CLOSE_MONITOR      HAL_Awss_Close_Monitor
#define HAL_AWSS_SWITCH_CHANNEL     HAL_Awss_Switch_Channel

// TSL communicate errcode definition
#define COMBO_TSL_SUCCESS            200 // success
#define COMBO_TSL_REQ_TOO_FAST       429 // request too fast, limited capacity of device
#define COMBO_TSL_PARAM_ERR          460 // TSL parse parameter error
#define COMBO_TSL_UNKNOWN_ERR        500 // Device system err
#define COMBO_TSL_FIND_PROPERTY_FAIL 5159
#define COMBO_TSL_FIND_EVENT_FAIL    5160
#define COMBO_TSL_FIND_SERVICE_FAIL  5161

#define COMBO_DEFAULT_DEVID          0
#define COMBO_TSL_ID                 "id"
#define COMBO_TSL_VERSION            "version"
#define COMBO_TSL_METHOD             "method"
#define COMBO_TSL_PARAMS             "params"
#define COMBO_TSL_PROTOCOL_VERSION   "1.0"
#define COMBO_TSL_EMPTY_ACK          "{}"
#define COMBO_TSL_ACK_FMT            "{\"id\":\"%d\",\"code\":%d,\"data\":%s}"
#define COMBO_TSL_METHOD_PROPER_SET  "thing.service.property.set"
#define COMBO_TSL_METHOD_PROPER_GET  "thing.service.property.get"
#define COMBO_TSL_METHOD_PROPER_POST "thing.event.property.post"
#define COMBO_COMMON_SERV_PREFIX     "thing.service."
#define COMBO_COMMON_SERV_POST_FMT \
    "{\"id\":\"%d\",\"method\":\"%s\",\"params\":%s,\"version\":\"%s\"}"

#define MAX_SSID_SIZE 32
#define MAX_PWD_SIZE  64
#ifndef MAX_SSID_LEN
#define MAX_SSID_LEN (MAX_SSID_SIZE + 1)
#endif

struct ieee80211_hdr {
    uint16_t frame_control;
    uint16_t duration_id;
    uint8_t  addr1[ETH_ALEN];
    uint8_t  addr2[ETH_ALEN];
    uint8_t  addr3[ETH_ALEN];
    uint16_t seq_ctrl;
    uint8_t  addr4[ETH_ALEN];
};

typedef struct {
    char    ssid[MAX_SSID_SIZE + 1];
    uint8_t bssid[ETH_ALEN];
    char    pwd[MAX_PWD_SIZE + 1];
} netmgr_ap_config_t;

typedef struct combo_rx_s {
    uint8_t *p_rx_buf;
    uint32_t rx_len;
    int      rx_msg_id;
} combo_rx_t;

typedef struct combo_tx_s {
    int     tx_msg_id;
    uint8_t tx_doing;
} combo_tx_t;

typedef struct combo_wifi_state_s {
    uint8_t ap_conn;
    uint8_t cloud_conn;
    uint8_t awss_run;
} combo_wifi_state_t;

typedef struct combo_user_bind_s {
    uint8_t bind_state;
    uint8_t sign_state;
    uint8_t need_sign;
} combo_user_bind_t;

static combo_user_bind_t g_combo_bind = {0};

static breeze_apinfo_t    apinfo;
static char               monitor_got_bssid = 0;
static netmgr_ap_config_t config;
uint8_t                   g_ble_state    = 0;
static uint8_t            g_disconn_flag = 0;
static wifi_prov_cb       prov_cb        = NULL;

static combo_wifi_state_t g_combo_wifi = {COMBO_AP_CONN_UNINIT, 0, 0};

static combo_event_cb        g_combo_event_cb        = NULL;
static combo_common_serv_cb  g_combo_common_serv_cb  = NULL;
static combo_property_set_cb g_combo_property_set_cb = NULL;
static combo_property_get_cb g_combo_property_get_cb = NULL;

static combo_rx_t g_combo_rx = {0};
static combo_tx_t g_combo_tx = {0};

static breeze_apinfo_t g_apinfo;
static aos_sem_t       wifi_connect_sem;

/* Device info(five elements) will be used by ble breeze */
/* ProductKey, ProductSecret, DeviceName, DeviceSecret, ProductID */
char     g_combo_pk[PRODUCT_KEY_LEN + 1]    = {0};
char     g_combo_ps[PRODUCT_SECRET_LEN + 1] = {0};
char     g_combo_dn[DEVICE_NAME_LEN + 1]    = {0};
char     g_combo_ds[DEVICE_SECRET_LEN + 1]  = {0};
uint32_t g_combo_pid                        = 0;
#endif

/*
 * Note:
 * the linkkit_event_monitor must not block and should run to complete fast
 * if user wants to do complex operation with much time,
 * user should post one task to do this, not implement complex operation in
 * linkkit_event_monitor
 */

static void linkkit_event_monitor(int event)
{
    switch (event) {
    case IOTX_AWSS_START: // AWSS start without enbale, just supports device discover
        // operate led to indicate user
        LOGD(TAG, "IOTX_AWSS_START");
        break;
    case IOTX_AWSS_ENABLE: // AWSS enable, AWSS doesn't parse awss packet until AWSS is enabled.
        LOGD(TAG, "IOTX_AWSS_ENABLE");
        // operate led to indicate user
        break;
    case IOTX_AWSS_LOCK_CHAN: // AWSS lock channel(Got AWSS sync packet)
        LOGD(TAG, "IOTX_AWSS_LOCK_CHAN");
        // operate led to indicate user
        break;
    case IOTX_AWSS_PASSWD_ERR: // AWSS decrypt passwd error
        LOGD(TAG, "IOTX_AWSS_PASSWD_ERR");
        // operate led to indicate user
        break;
    case IOTX_AWSS_GOT_SSID_PASSWD:
        LOGD(TAG, "IOTX_AWSS_GOT_SSID_PASSWD");
        // operate led to indicate user
        //set_net_state(GOT_AP_SSID);
        break;
    case IOTX_AWSS_CONNECT_ADHA: // AWSS try to connnect adha (device
        // discover, router solution)
        LOGD(TAG, "IOTX_AWSS_CONNECT_ADHA");
        // operate led to indicate user
        break;
    case IOTX_AWSS_CONNECT_ADHA_FAIL: // AWSS fails to connect adha
        LOGD(TAG, "IOTX_AWSS_CONNECT_ADHA_FAIL");
        // operate led to indicate user
        break;
    case IOTX_AWSS_CONNECT_AHA: // AWSS try to connect aha (AP solution)
        LOGD(TAG, "IOTX_AWSS_CONNECT_AHA");
        // operate led to indicate user
        break;
    case IOTX_AWSS_CONNECT_AHA_FAIL: // AWSS fails to connect aha
        LOGD(TAG, "IOTX_AWSS_CONNECT_AHA_FAIL");
        // operate led to indicate user
        break;
    case IOTX_AWSS_SETUP_NOTIFY: // AWSS sends out device setup information
        // (AP and router solution)
        LOGD(TAG, "IOTX_AWSS_SETUP_NOTIFY");
        // operate led to indicate user
        break;
    case IOTX_AWSS_CONNECT_ROUTER: // AWSS try to connect destination router
        LOGD(TAG, "IOTX_AWSS_CONNECT_ROUTER");
        // operate led to indicate user
        break;
    case IOTX_AWSS_CONNECT_ROUTER_FAIL: // AWSS fails to connect destination
        // router.
        LOGD(TAG, "IOTX_AWSS_CONNECT_ROUTER_FAIL");
        //set_net_state(CONNECT_AP_FAILED);
        // operate led to indicate user
        break;
    case IOTX_AWSS_GOT_IP: // AWSS connects destination successfully and got
        // ip address
        LOGD(TAG, "IOTX_AWSS_GOT_IP");
        // operate led to indicate user
        break;
    case IOTX_AWSS_SUC_NOTIFY: // AWSS sends out success notify (AWSS
        // sucess)
        LOGD(TAG, "IOTX_AWSS_SUC_NOTIFY");
        // operate led to indicate user
        break;
    case IOTX_AWSS_BIND_NOTIFY: // AWSS sends out bind notify information to
        // support bind between user and device
        LOGD(TAG, "IOTX_AWSS_BIND_NOTIFY");
        // operate led to indicate user
        //user_example_ctx_t *user_example_ctx = user_example_get_ctx();
        //user_example_ctx->bind_notified = 1;
        break;
    case IOTX_AWSS_ENABLE_TIMEOUT: // AWSS enable timeout
        // user needs to enable awss again to support get ssid & passwd of router
        LOGD(TAG, "IOTX_AWSS_ENALBE_TIMEOUT");
        // operate led to indicate user
        break;
    case IOTX_CONN_CLOUD: // Device try to connect cloud
        LOGD(TAG, "IOTX_CONN_CLOUD");
        // operate led to indicate user
        set_net_state(GOT_AP_SSID);
        break;
    case IOTX_CONN_CLOUD_FAIL: // Device fails to connect cloud, refer to
        // net_sockets.h for error code
        LOGD(TAG, "IOTX_CONN_CLOUD_FAIL");
#ifdef EN_COMBO_NET
        combo_set_cloud_state(0);
#endif
        set_net_state(CONNECT_CLOUD_FAILED);
        // operate led to indicate user
        break;
    case IOTX_CONN_CLOUD_SUC: // Device connects cloud successfully
        //set_net_state(CONNECT_CLOUD_SUCCESS);
        LOGD(TAG, "IOTX_CONN_CLOUD_SUC");
#ifdef EN_COMBO_NET
        combo_set_cloud_state(1);
#endif
        set_net_state(CONNECT_CLOUD_SUCCESS);
        // operate led to indicate user
        break;
    case IOTX_RESET: // Linkkit reset success (just got reset response from
        // cloud without any other operation)
        LOGD(TAG, "IOTX_RESET");
        break;
    case IOTX_CONN_REPORT_TOKEN_SUC:
#ifdef EN_COMBO_NET
        combo_token_report_notify();
#endif
        LOGD(TAG, "---- report token success ----");
        break;
    default:
        break;
    }
}

#ifdef EN_COMBO_NET
static void combo_status_change_cb(breeze_event_t event)
{
    switch (event) {
    case CONNECTED:
        LOGD(TAG, "Ble Connected");
        g_ble_state    = 1;
        g_disconn_flag = 0;
        if (g_combo_bind.bind_state) {
            g_combo_bind.need_sign = 1;
        } else {
            g_combo_bind.need_sign = 0;
        }
        break;

    case DISCONNECTED:
        LOGD(TAG, "Ble Disconnected");
        g_ble_state             = 0;
        g_combo_bind.sign_state = 0;
        g_combo_bind.need_sign  = 0;
        g_combo_tx.tx_doing     = 0;
        aos_post_event(EV_BZ_COMBO, COMBO_EVT_CODE_RESTART_ADV, 0);
        break;

    case AUTHENTICATED:
        LOGD(TAG, "Ble Authenticated");
        break;

    case TX_DONE:
        g_combo_tx.tx_doing = 0;
        if (g_disconn_flag && g_ble_state) {
            LOGD(TAG, "Payload TX Done");
            breeze_disconnect_ble();
            g_disconn_flag = 0;
        }
        break;

    case EVT_USER_BIND:
        LOGD(TAG, "Ble bind");
        g_combo_bind.bind_state = 1;
        awss_clear_reset();
        break;

    case EVT_USER_UNBIND:
        LOGD(TAG, "Ble unbind");
        g_combo_bind.bind_state = 0;
        break;

    case EVT_USER_SIGNED:
        LOGD(TAG, "Ble sign pass");
        g_combo_bind.sign_state = 1;
        break;

    default:
        break;
    }
}

static void combo_set_dev_status_cb(uint8_t *buffer, uint32_t length)
{
    if ((g_combo_rx.p_rx_buf != NULL) || (g_combo_rx.rx_len != 0)) {
        LOGD(TAG, "combo set dev rx queue full!!");
    } else {
        if (g_combo_bind.need_sign && !g_combo_bind.sign_state) {
            LOGD(TAG, "BLE_SET discarded because not signed");
        } else {
            LOGD(TAG, "BLE_SET:%.*s", length, buffer);
        }
    }
}

static void combo_get_dev_status_cb(uint8_t *buffer, uint32_t length)
{
    //LOGD(TAG, "BLE_QUE:%.*s", length, buffer);
    if ((g_combo_rx.p_rx_buf != NULL) || (g_combo_rx.rx_len != 0)) {
        LOGD(TAG, "combo get dev rx queue full!!");
        aos_post_event(EV_BZ_COMBO, COMBO_EVT_CODE_TSL_ACK, COMBO_TSL_REQ_TOO_FAST);
    } else {
        if (g_combo_bind.need_sign && !g_combo_bind.sign_state) {
            LOGD(TAG, "BLE_QUE discarded because not signed");
        } else {
            g_combo_rx.p_rx_buf = buffer;
            g_combo_rx.rx_len   = length;
            aos_post_event(EV_BZ_COMBO, COMBO_EVT_CODE_RX_PAYLOAD, 0);
        }
    }
}

static void combo_apinfo_rx_cb(breeze_apinfo_t *ap)
{
    if (!ap) {
        LOGD(TAG, "combo apinfo rx null!!");
        return;
    }

    memcpy(&apinfo, ap, sizeof(apinfo));
    aos_post_event(EV_BZ_COMBO, COMBO_EVT_CODE_AP_INFO, (unsigned long)&apinfo);
}

static int combo_80211_frame_callback(char *buf, int length, enum AWSS_LINK_TYPE link_type,
                                      int with_fcs, signed char rssi)
{
    uint8_t               ssid[MAX_SSID_LEN] = {0}, bssid[ETH_ALEN] = {0};
    struct ieee80211_hdr *hdr;
    int                   fc;
    int                   ret = -1;

    if (link_type != AWSS_LINK_TYPE_NONE) {
        return -1;
    }
    hdr = (struct ieee80211_hdr *)buf;

    fc = hdr->frame_control;
    if (!HAL_IEEE80211_IS_BEACON(fc) && !HAL_IEEE80211_IS_PROBE_RESP(fc)) {
        return -1;
    }
    ret = HAL_IEEE80211_GET_BSSID((uint8_t *)hdr, bssid);
    if (ret < 0) {
        return -1;
    }
    if (memcmp(config.bssid, bssid, ETH_ALEN) != 0) {
        return -1;
    }
    ret = HAL_IEEE80211_GET_SSID((uint8_t *)hdr, length, ssid);
    if (ret < 0) {
        return -1;
    }
    monitor_got_bssid = 1;
    strncpy(config.ssid, (char *)ssid, sizeof(config.ssid) - 1);
    return 0;
}

static char combo_is_str_asii(char *str)
{
    for (uint32_t i = 0; str[i] != '\0'; ++i) {
        if ((uint8_t)str[i] > 0x7F) {
            return 0;
        }
    }
    return 1;
}

static void combo_connect_ap(breeze_apinfo_t *info)
{
    int            ms_cnt       = 0;
    uint8_t        ap_connected = 0;
    ap_scan_info_t scan_result;
    int            ap_scan_result = -1;

    memset(&config, 0, sizeof(netmgr_ap_config_t));
    if (!info)
        return;

    strncpy(config.ssid, info->ssid, sizeof(config.ssid) - 1);
    strncpy(config.pwd, info->pw, sizeof(config.pwd) - 1);
    if (info->apptoken_len > 0) {
        memcpy(config.bssid, info->bssid, ETH_ALEN);
        awss_set_token(info->apptoken, info->token_type);
    }
    // get region information
    if (info->region_type == REGION_TYPE_ID) {
        LOGD(TAG, "info->region_id: %d", info->region_id);
        iotx_guider_set_dynamic_region(info->region_id);
    } else if (info->region_type == REGION_TYPE_MQTTURL) {
        LOGD(TAG, "info->region_mqtturl: %s", info->region_mqtturl);
        iotx_guider_set_dynamic_mqtt_url(info->region_mqtturl);
    } else {
        LOGD(TAG, "REGION TYPE not supported");
        iotx_guider_set_dynamic_region(IOTX_CLOUD_REGION_INVALID);
    }

    /* indicate led state*/
    set_net_state(GOT_AP_SSID);
    wifi_prov_result_t res;
    memset(&res, 0, sizeof(wifi_prov_result_t));
    strncpy(res.ssid, info->ssid, sizeof(res.ssid) - 1);
    strncpy(res.password, info->pw, sizeof(res.password) - 1);

    if (prov_cb)
        prov_cb(0, WIFI_RPOV_EVENT_GOT_RESULT, &res);

    aos_msleep(3000);

    netmgr_hdl_t hdl = netmgr_get_handle("wifi");
    if (hdl) {
        while (ms_cnt < 30000) {
            if (!netmgr_is_gotip(hdl)) {
                aos_msleep(500);
                ms_cnt += 500;
                LOGD(TAG, "wait ms_cnt(%d)", ms_cnt);
            } else {
                LOGD(TAG, "AP connected");
                ap_connected = 1;
                break;
            }
        }
    }

    if (!ap_connected) {
        uint16_t err_code = 0;

        memset(&scan_result, 0, sizeof(ap_scan_info_t));
        LOGD(TAG, "start awss_apscan_process");
        ap_scan_result = awss_apscan_process(NULL, config.ssid, &scan_result);
        LOGD(TAG, "stop awss_apscan_process");
        if ((ap_scan_result == 0) && (scan_result.found)) {
            if (scan_result.rssi < -70) {
                err_code = 0xC4E1; // rssi too low
            } else {
                err_code =
                    0xC4E3; // AP connect fail(Authentication fail or Association fail or AP exeption)
            }
            // should set all ap info to err msg
        } else {
            err_code = 0xC4E0; // AP not found
        }

        if (g_ble_state) {
            uint8_t ble_rsp[DEV_ERRCODE_MSG_MAX_LEN + 8] = {0};
            uint8_t ble_rsp_idx                          = 0;
            ble_rsp[ble_rsp_idx++]                       = 0x01; // Notify Code Type
            ble_rsp[ble_rsp_idx++]                       = 0x01; // Notify Code Length
            ble_rsp[ble_rsp_idx++]                       = 0x02; // Notify Code Value, 0x02-fail
            ble_rsp[ble_rsp_idx++]                       = 0x03; // Notify SubErrcode Type
            ble_rsp[ble_rsp_idx++] = sizeof(err_code);           // Notify SubErrcode Length
            memcpy(ble_rsp + ble_rsp_idx, (uint8_t *)&err_code,
                   sizeof(err_code)); // Notify SubErrcode Value
            ble_rsp_idx += sizeof(err_code);
            breeze_post(ble_rsp, ble_rsp_idx);
        }
    }
}

void wifi_connect_handler(void *arg)
{
    while (1) {
        aos_sem_wait(&wifi_connect_sem, AOS_WAIT_FOREVER);
        combo_connect_ap(&g_apinfo);
    }
}

static void combo_control_handler(uint8_t *buffer, uint32_t length)
{
    cJSON *root   = NULL;
    cJSON *msg_id = NULL;
    cJSON *method = NULL;
    cJSON *params = NULL;
    //cJSON *version = NULL;
    int     response_len = 0;
    uint8_t json_valid   = 1;

    // Parse json payload from peer device
    root = cJSON_Parse(buffer);
    if ((root != NULL) && cJSON_IsObject(root)) {
        msg_id = cJSON_GetObjectItem(root, COMBO_TSL_ID);
        method = cJSON_GetObjectItem(root, COMBO_TSL_METHOD);
        params = cJSON_GetObjectItem(root, COMBO_TSL_PARAMS);
        if ((msg_id != NULL) && (method != NULL) && (params != NULL) && cJSON_IsObject(params)) {
            g_combo_rx.rx_msg_id = atoi(msg_id->valuestring);
        } else {
            LOGD(TAG, "combo json params Error(%s)(%d)", (char *)buffer, length);
            json_valid = 0;
        }
    } else {
        LOGD(TAG, "combo json parse Error");
        json_valid = 0;
    }

    // Process payload from peer device
    if (json_valid) {
        char *p_param = cJSON_PrintUnformatted(params);
        ;
        // payload received from peer device is valid
        if ((strncmp(method->valuestring, COMBO_TSL_METHOD_PROPER_SET,
                     strlen(COMBO_TSL_METHOD_PROPER_SET)) == 0) &&
            g_combo_property_set_cb) {
            combo_tsl_ack(COMBO_TSL_SUCCESS, COMBO_TSL_EMPTY_ACK, strlen(COMBO_TSL_EMPTY_ACK));
            g_combo_property_set_cb(COMBO_DEFAULT_DEVID, p_param, strlen(p_param));
        } else if ((strncmp(method->valuestring, COMBO_TSL_METHOD_PROPER_GET,
                            strlen(COMBO_TSL_METHOD_PROPER_GET)) == 0) &&
                   g_combo_property_get_cb) {
            char *response = NULL;
            g_combo_property_get_cb(COMBO_DEFAULT_DEVID, p_param, strlen(p_param), &response,
                                    &response_len);
            if (response != NULL && response_len > 0) {
                // property get response exist
                combo_tsl_ack(COMBO_TSL_SUCCESS, response, response_len);
                HAL_Free(response);
            } else {
                combo_tsl_ack(COMBO_TSL_FIND_PROPERTY_FAIL, COMBO_TSL_EMPTY_ACK,
                              strlen(COMBO_TSL_EMPTY_ACK));
            }
        } else if ((strncmp(method->valuestring, COMBO_COMMON_SERV_PREFIX,
                            strlen(COMBO_COMMON_SERV_PREFIX)) == 0) &&
                   g_combo_common_serv_cb) {
            combo_tsl_ack(COMBO_TSL_SUCCESS, COMBO_TSL_EMPTY_ACK, strlen(COMBO_TSL_EMPTY_ACK));
            g_combo_common_serv_cb(COMBO_DEFAULT_DEVID,
                                   method->valuestring + strlen(COMBO_COMMON_SERV_PREFIX),
                                   strlen(method->valuestring) - strlen(COMBO_COMMON_SERV_PREFIX),
                                   p_param, strlen(p_param));
        } else {
            LOGD(TAG, "combo no callback found");
            combo_tsl_ack(COMBO_TSL_FIND_SERVICE_FAIL, COMBO_TSL_EMPTY_ACK,
                          strlen(COMBO_TSL_EMPTY_ACK));
        }
        if (p_param) {
            HAL_Free(p_param);
        }
    } else {
        combo_tsl_ack(COMBO_TSL_PARAM_ERR, COMBO_TSL_EMPTY_ACK, strlen(COMBO_TSL_EMPTY_ACK));
    }

    if (root) {
        cJSON_Delete(root);
    }
}

static void combo_restart_ble_adv()
{
    int32_t valid_ap          = 0;
    char    wifi_ssid[32 + 1] = "\0";
    int     wifi_ssid_len     = sizeof(wifi_ssid);

    aos_kv_get(KV_WIFI_SSID, wifi_ssid, &wifi_ssid_len);
    if (strlen(wifi_ssid) > 0)
        valid_ap = 1;
    else
        valid_ap = 0;

    if (!g_ble_state) {
        if (!g_combo_wifi.cloud_conn) {
            if ((valid_ap && (g_combo_wifi.ap_conn == COMBO_AP_DISCONNECTED)) || !valid_ap) {
                // combo device ap is not configed
                if (g_combo_wifi.awss_run) {
                    // in awss mode, should advertising and wait for wifi config
                    // If device is not advertising, it's a redundant operation
                    breeze_stop_advertising();
                    aos_msleep(300); // wait for adv stop
                    breeze_start_advertising(g_combo_bind.bind_state, COMBO_AWSS_NEED);
                } else {
                    breeze_stop_advertising();
                }
            } else {
                // combo device ap already configed
                if (g_combo_bind.bind_state) {
                    // support offline bind, should advertising and wait for offline control over ble
                    // If device is not advertising, it's a redundant operation
                    breeze_stop_advertising();
                    aos_msleep(300); // wait for adv stop
                    breeze_start_advertising(g_combo_bind.bind_state, COMBO_AWSS_NOT_NEED);
                } else {
                    breeze_stop_advertising();
                }
            }
        }
    }
}

static void combo_service_evt_handler(input_event_t *event, void *priv_data)
{
    if (event->type != EV_BZ_COMBO) {
        return;
    }

    if (event->code == COMBO_EVT_CODE_FULL_REPORT) {
        if (g_combo_event_cb) {
            g_combo_event_cb(COMBO_EVT_CODE_FULL_REPORT);
        }
    } else if (event->code == COMBO_EVT_CODE_AP_INFO) {
        memcpy((void *)&g_apinfo, (void *)event->value, sizeof(breeze_apinfo_t));
        aos_sem_signal(&wifi_connect_sem);
    } else if (event->code == COMBO_EVT_CODE_RESTART_ADV) {
        if (!g_ble_state) {
            combo_restart_ble_adv();
        }
    } else if (event->code == COMBO_EVT_CODE_RX_PAYLOAD) {
        if ((g_combo_rx.p_rx_buf != NULL) && (g_combo_rx.rx_len > 0)) {
            combo_control_handler(g_combo_rx.p_rx_buf, g_combo_rx.rx_len);
        }
        g_combo_rx.p_rx_buf = NULL;
        g_combo_rx.rx_len   = 0;
    } else if (event->code == COMBO_EVT_CODE_TSL_ACK) {
        combo_tsl_ack((uint16_t)event->value, COMBO_TSL_EMPTY_ACK, strlen(COMBO_TSL_EMPTY_ACK));
    } else {
        LOGD(TAG, "Unknown combo event");
    }
}

static int reverse_byte_array(uint8_t *byte_array, int byte_len)
{
    uint8_t temp = 0;
    int     i    = 0;
    if (!byte_array) {
        LOGD(TAG, "reverse_mac invalid params!");
        return -1;
    }
    for (i = 0; i < (byte_len / 2); i++) {
        temp                         = byte_array[i];
        byte_array[i]                = byte_array[byte_len - i - 1];
        byte_array[byte_len - i - 1] = temp;
    }
    return 0;
}

int combo_net_init(wifi_prov_cb cb)
{
    breeze_dev_info_t dinfo = {0};

    if ((0 == HAL_GetProductKey(g_combo_pk)) || (0 == HAL_GetDeviceName(g_combo_dn)) ||
        (0 == HAL_GetProductSecret(g_combo_ps)) || (0 == HAL_GetDeviceSecret(g_combo_ds)) ||
        (0 != aos_kv_getint("hal_devinfo_pid", &g_combo_pid))) {
        LOGE(TAG, "missing ProductKey/ProductSecret/DeviceName/DeviceSecret/PID!");
        return -1;
    }

    prov_cb = cb;
    aos_sem_new(&wifi_connect_sem, 0);
    aos_task_new("wifi_connect_task", wifi_connect_handler, NULL, 1536);

    aos_register_event_filter(EV_BZ_COMBO, combo_service_evt_handler, NULL);
    g_combo_bind.bind_state = breeze_get_bind_state();
    if ((strlen(g_combo_pk) > 0) && (strlen(g_combo_ps) > 0) && (strlen(g_combo_dn) > 0) &&
        (strlen(g_combo_ds) > 0) && g_combo_pid > 0) {
        uint8_t combo_adv_mac[6] = {0};
        wifi_getmac(combo_adv_mac);

        // Set wifi mac to breeze awss adv, to notify app this is a wifi dev
        // Awss use wifi module device info.
        // Only use BT mac when use breeze and other bluetooth communication functions
        reverse_byte_array(combo_adv_mac, 6);
        dinfo.product_id     = g_combo_pid;
        dinfo.product_key    = g_combo_pk;
        dinfo.product_secret = g_combo_ps;
        dinfo.device_name    = g_combo_dn;
        dinfo.device_secret  = g_combo_ds;
        dinfo.dev_adv_mac    = combo_adv_mac;
        breeze_awss_init(&dinfo, combo_status_change_cb, combo_set_dev_status_cb,
                         combo_get_dev_status_cb, combo_apinfo_rx_cb, NULL);
        breeze_awss_start();
        iotx_event_regist_cb(linkkit_event_monitor);
        combo_set_awss_state(1);
        combo_set_ap_state(COMBO_AP_DISCONNECTED);
    } else {
        LOGE(TAG, "combo device info not set!");
    }
    return 0;
}

int combo_net_deinit()
{
    breeze_awss_stop();
    return 0;
}

uint8_t combo_ble_conn_state(void)
{
    return g_ble_state;
}

void combo_set_cloud_state(uint8_t cloud_connected)
{
    g_combo_wifi.cloud_conn = cloud_connected;
    if (!g_ble_state) {
        aos_post_event(EV_BZ_COMBO, COMBO_EVT_CODE_RESTART_ADV, 0);
    }
}

void combo_set_ap_state(uint8_t ap_connected)
{
    g_combo_wifi.ap_conn = ap_connected;
    if (!g_ble_state) {
        //aos_post_event(EV_BZ_COMBO, COMBO_EVT_CODE_RESTART_ADV, 0);
    }
}

void combo_set_awss_state(uint8_t awss_running)
{
    g_combo_wifi.awss_run = awss_running;
    if (!g_ble_state) {
        aos_post_event(EV_BZ_COMBO, COMBO_EVT_CODE_RESTART_ADV, 0);
    }
}

void combo_reg_evt_cb(combo_event_cb event_cb)
{
    g_combo_event_cb = event_cb;
}

void combo_reg_common_serv_cb(combo_common_serv_cb common_serv_cb)
{
    g_combo_common_serv_cb = common_serv_cb;
}

void combo_reg_property_set_cb(combo_property_set_cb property_set_cb)
{
    g_combo_property_set_cb = property_set_cb;
}

void combo_reg_property_get_cb(combo_property_get_cb property_get_cb)
{
    g_combo_property_get_cb = property_get_cb;
}

void combo_ap_conn_notify(void)
{
    uint8_t rsp[] = {0x01, 0x01, 0x01};
    if (g_ble_state) {
        breeze_post(rsp, sizeof(rsp));
    }
}

void combo_token_report_notify(void)
{
    uint8_t rsp[] = {0x01, 0x01, 0x03};
    if (g_ble_state) {
        breeze_post(rsp, sizeof(rsp));
        g_disconn_flag = 1;
    }
}

static void combo_wait_for_tx_idle(void)
{
    while (g_combo_tx.tx_doing && g_ble_state) {
        aos_msleep(10);
    }
}

static void combo_send_data_use_ind(uint8_t cmd, uint8_t *buffer, uint32_t length)
{
    LOGD(TAG, "combo sending(0x%02x)", cmd);
    combo_wait_for_tx_idle();
    g_combo_tx.tx_doing = 1;
    breeze_post_ext(cmd, buffer, length);
    combo_wait_for_tx_idle();
}

static void combo_send_data_use_noti(uint8_t cmd, uint8_t *buffer, uint32_t length)
{
    LOGD(TAG, "combo sending(0x%02x)", cmd);
    combo_wait_for_tx_idle();
    g_combo_tx.tx_doing = 1;
    breeze_post_ext_fast(cmd, buffer, length);
    combo_wait_for_tx_idle();
}

int combo_tsl_ack(uint16_t err_code, uint8_t *buffer, uint32_t length)
{
    char *   rsp_src = NULL;
    uint16_t rsp_len = 0;

    rsp_len += strlen(COMBO_TSL_ACK_FMT);
    rsp_len += 10; // msg id str len
    rsp_len += 5;  // err code str len
    rsp_len += length;

    rsp_src = (char *)HAL_Malloc(rsp_len);
    if (!rsp_src) {
        LOGE(TAG, "combo_tsl_ack: alloc fail!");
        return -1;
    }

    HAL_Snprintf(rsp_src, rsp_len, COMBO_TSL_ACK_FMT, g_combo_rx.rx_msg_id, err_code,
                 (char *)buffer);
    LOGD(TAG, "combo_tsl_ack:%.*s", strlen(rsp_src), rsp_src);
    combo_send_data_use_ind(0x03, rsp_src, strlen(rsp_src));
    LOGD(TAG, "combo_tsl_ack: %d > %d", rsp_len, strlen(rsp_src));
    HAL_Free(rsp_src);
}

int combo_status_report(uint8_t *buffer, uint32_t length)
{
    char *   rpt_src = NULL;
    uint16_t rpt_len = 0;

    rpt_len += strlen(COMBO_COMMON_SERV_POST_FMT);
    rpt_len += 10; // msg id str len
    rpt_len += strlen(COMBO_TSL_METHOD_PROPER_POST);
    rpt_len += length;
    rpt_len += strlen(COMBO_TSL_PROTOCOL_VERSION);

    rpt_src = (char *)HAL_Malloc(rpt_len);
    if (!rpt_src) {
        LOGE(TAG, "combo_status_report: alloc fail!");
        return -1;
    }

    HAL_Snprintf(rpt_src, rpt_len, COMBO_COMMON_SERV_POST_FMT, g_combo_tx.tx_msg_id++,
                 COMBO_TSL_METHOD_PROPER_POST, (char *)buffer, COMBO_TSL_PROTOCOL_VERSION);
    LOGD(TAG, "combo_status_report:%.*s", strlen(rpt_src), rpt_src);
    combo_send_data_use_ind(0x01, rpt_src, strlen(rpt_src));
    LOGD(TAG, "combo_status_report: %d > %d", rpt_len, strlen(rpt_src));
    HAL_Free(rpt_src);
}

static void handle_ble_awss_cmd(char *wbuf, int wbuf_len, int argc, char **argv)
{
    LOGD(TAG, "ble");
    do_awss_reboot();
}

void cli_reg_cmd_ble_awss(void)
{
    static const struct cli_command cmd_info = {
        .name     = "ble_awss",
        .help     = "ble_awss [start]",
        .function = handle_ble_awss_cmd,
    };
    aos_cli_register_command(&cmd_info);
}
#endif

#ifdef AWSS_BATCH_DEVAP_ENABLE
#define DEV_AP_ZCONFIG_TIMEOUT_MS 120000 // (ms)
extern void    awss_set_config_press(uint8_t press);
extern uint8_t awss_get_config_press(void);
extern void    zconfig_80211_frame_filter_set(uint8_t filter, uint8_t fix_channel);

static aos_timer_t dev_ap_zconfig_timeout_timer;
static uint8_t     g_dev_ap_zconfig_timer = 0; // this timer create once and can restart
static uint8_t     g_dev_ap_zconfig_run   = 0;

static void timer_func_devap_zconfig_timeout(void *arg1, void *arg2)
{
    LOG("%s run\n", __func__);

    if (awss_get_config_press()) {
        // still in zero wifi provision stage, should stop and switch to dev ap
        extern int wifi_prov_method;
        wifi_prov_method = WIFI_PROVISION_SL_DEV_AP;
        aos_kv_setint("wprov_method", wifi_prov_method);
        app_sys_set_boot_reason(BOOT_REASON_WIFI_CONFIG);
        aos_reboot();
    } else {
        // zero wifi provision finished
    }

    awss_set_config_press(0);
    zconfig_80211_frame_filter_set(0xFF, 0xFF);
    g_dev_ap_zconfig_run = 0;
    aos_timer_stop(&dev_ap_zconfig_timeout_timer);
}

static void awss_dev_ap_switch_to_zeroconfig(void *p)
{
    LOG("%s run\n", __func__);
    // Stop dev ap wifi provision
    awss_dev_ap_stop();
    // Start and enable zero wifi provision
    iotx_event_regist_cb(linkkit_event_monitor);
    awss_set_config_press(1);

    // Start timer to count duration time of zero provision timeout
    if (!g_dev_ap_zconfig_timer) {
        aos_timer_new(&dev_ap_zconfig_timeout_timer, timer_func_devap_zconfig_timeout, NULL,
                      DEV_AP_ZCONFIG_TIMEOUT_MS, 0);
        g_dev_ap_zconfig_timer = 1;
    }
    aos_timer_start(&dev_ap_zconfig_timeout_timer);

    LOG("%s exit\n", __func__);
    aos_task_exit(0);
}

static int awss_dev_ap_modeswitch_cb(uint8_t awss_new_mode, uint8_t new_mode_timeout,
                                     uint8_t fix_channel)
{
    if ((awss_new_mode == 0) && !g_dev_ap_zconfig_run) {
        g_dev_ap_zconfig_run = 1;
        // Only receive zero provision packets
        zconfig_80211_frame_filter_set(0x00, fix_channel);
        LOG("switch to awssmode %d, mode_timeout %d, chan %d\n", 0x00, new_mode_timeout,
            fix_channel);
        // switch to zero config
        aos_task_new("devap_to_zeroconfig", awss_dev_ap_switch_to_zeroconfig, NULL, 2048);
    }
}

int app_awss_dev_ap_reg_modeswit_cb()
{
    awss_dev_ap_reg_modeswit_cb(awss_dev_ap_modeswitch_cb);
}
#endif